package com.jcs;

import weka.core.Instances;
import weka.classifiers.trees.M5P;
import weka.core.converters.ConverterUtils.DataSource;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.Normalize;
import java.io.*;

public class DecisionTreeExample {

    public static void main(String[] args) throws Exception {
        // 示例数据
        // 每一行代表一个样本，每一列代表一个特征  开度
        double[][] x = {
                { 100.0,  100,  52, 60},
                { 150.0,  100,  54, 62},
                { 150.0,  80,   51, 60},
                { 200.0,  90,   56, 62},
                { 100.0,  90,   55, 61},
                { 150.0,  80,   52, 61},
                { 200.0,  100,  51, 61},
                { 100.0,  100,  58, 66},
                { 150.0,  90,   57, 65}
        };

        // 目标变量 对应前面的当前室温
        double[] y = {29, 28, 29, 27, 29, 29, 28, 29, 28};

        // 将数据转换为 Weka 的 Instances 对象
        Instances instances = createInstances(x, y);

        // 数据标准化
        Normalize normalize = new Normalize();
        normalize.setInputFormat(instances);
        Instances normalizedInstances = Filter.useFilter(instances, normalize);

        // 检查数据中的异常值
        checkDataQuality(normalizedInstances);

        // 创建 M5P 回归树分类器
        M5P tree = new M5P();

        // 设置模型参数
        String[] options = {"-M", "2", "-U"};
        // 最小实例数为2，启用未剪枝
        tree.setOptions(options);

        // 训练模型
        tree.buildClassifier(normalizedInstances);

        // 输出回归树模型
        System.out.println(tree);

        // 手动检查特征重要性
        checkFeatureImportance(tree, normalizedInstances);

        // 保存模型
        saveModel(tree, "m5p_model.model");

        // 加载模型并进行预测
        M5P loadedTree = loadModel("m5p_model.model");

        // 预测新数据点 面积 阀门开度 回水温度 新特征
        double[] newX = {50,  100,  55, 62};
        double predictedY = predict(loadedTree, newX, normalizedInstances);
        System.out.println("预测值: " + predictedY);
    }

    // 将数据转换为 Weka 的 Instances 对象
    private static Instances createInstances(double[][] x, double[] y) throws Exception {
        // 定义特征属性
        String[] featureNames = {"面积", "阀门开度", "回水温度", "供水温度"};
        String targetName = "当前室温";

        // 创建属性
        weka.core.FastVector attributes = new weka.core.FastVector<>(x[0].length + 1);
        for (String name : featureNames) {
            attributes.addElement(new weka.core.Attribute(name));
        }
        attributes.addElement(new weka.core.Attribute(targetName));

        // 创建 Instances 对象
        Instances instances = new Instances("Dataset", attributes, x.length);

        // 添加数据
        for (int i = 0; i < x.length; i++) {
            double[] values = new double[x[0].length + 1];
            System.arraycopy(x[i], 0, values, 0, x[0].length);
            values[x[0].length] = y[i];
            instances.add(new weka.core.DenseInstance(1.0, values));
        }

        // 设置类别索引
        instances.setClassIndex(instances.numAttributes() - 1);

        return instances;
    }

    // 保存模型
    private static void saveModel(Object model, String filename) throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
            oos.writeObject(model);
        }
    }

    // 加载模型
    private static M5P loadModel(String filename) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
            return (M5P) ois.readObject();
        }
    }

    // 预测新数据点
    private static double predict(M5P tree, double[] newX, Instances trainingData) throws Exception {
        // 创建新的 Instance 对象
        Instances testData = new Instances(trainingData, 0);
        double[] values = new double[trainingData.numAttributes()];
        System.arraycopy(newX, 0, values, 0, newX.length);
        values[trainingData.numAttributes() - 1] = Double.NaN; // 目标变量未知
        weka.core.Instance instance = new weka.core.DenseInstance(1.0, values);
        testData.add(instance);

        // 设置类别索引
        testData.setClassIndex(trainingData.classIndex());

        // 预测
        double predictedY = tree.classifyInstance(testData.instance(0));
        return predictedY;
    }

    // 检查数据中的异常值
    private static void checkDataQuality(Instances instances) {
        for (int i = 0; i < instances.numInstances(); i++) {
            for (int j = 0; j < instances.numAttributes(); j++) {
                double value = instances.instance(i).value(j);
                if (Double.isNaN(value) || Double.isInfinite(value)) {
                    System.out.println("Warning: NaN or infinite value found at instance " + i + ", attribute " + j);
                }
            }
        }
    }

    // 手动检查特征重要性
    private static void checkFeatureImportance(M5P tree, Instances instances) {
        // 输出模型的详细信息
        System.out.println(tree.toString());

        // 分析模型输出，手动检查特征重要性
        // 例如，查找哪些特征被用于分割节点
        String modelDetails = tree.toString();
        System.out.println("Model Details: " + modelDetails);
    }
}